<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
<script>
 /* 
 Vue 框架通过数据双向绑定和虚拟 DOM 技术，帮我们处理了前端开发中最脏最累的 DOM 操作部分，
 我们不再需要去考虑如何操作 DOM 以及如何最高效地操作 DOM；但 Vue 项目中仍然存在项目首屏优化、
 Webpack 编译配置优化等问题，所以我们仍然需要去关注 Vue 项目性能方面的优化，使项目具有更高效的性能、
 更好的用户体验，从而对自己的项目进行优化起到帮助。本文内容分为以下三部分组成：
   1:Vue 代码层面的优化；
   2:webpack 配置层面的优化；
   3:基础的 Web 技术层面的优化。

 代码层优化：
   1: v-if 和 v-show 区分使用场景 ?
    v-if 是 真正 的条件渲染，因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建；也是惰性的：
    如果在初始渲染时条件为假，则什么也不做——直到条件第一次变为真时，才会开始渲染条件块。
    v-show 就简单得多， 不管初始条件是什么，元素总是会被渲染，并且只是简单地基于 CSS 的 display 属性进行切换。
    所以，v-if 适用于在运行时很少改变条件，不需要频繁切换条件的场景；v-show 则适用于需要非常频繁切换条件的场景。
 
   2:computed 和 watch 区分使用场景 ?
    computed： 是计算属性，依赖其它属性值，并且 computed 的值有缓存，只有它依赖的属性值发生改变，下一次获取 computed 的值时才会重新计算 computed  的值；
    watch： 更多的是「观察」的作用，类似于某些数据的监听回调 ，每当监听的数据变化时都会执行回调进行后续操作；
   
   3:v-for 遍历必须为 item 添加 key，且避免同时使用 v-if
     （1）v-for 遍历必须为 item 添加 key
      在列表数据进行遍历渲染时，需要为每一项 item 设置唯一 key 值，方便 Vue.js 内部机制精准找到该条列表数据。当 state 更新时，新的状态值和旧的状态值对比，较快地定位到 diff 。
     （2）v-for 遍历避免同时使用 v-if
      v-for 比 v-if 优先级高，如果每一次都需要遍历整个数组，将会影响速度，尤其是当之需要渲染很小一部分的时候，必要情况下应该替换成 computed 属性。
 
    4:长列表性能优化
    Vue 会通过 Object.defineProperty 对数据进行劫持，来实现视图响应数据的变化，
    然而有些时候我们的组件就是纯粹的数据展示，不会有任何改变，我们就不需要 Vue 来劫持我们的数据，
    在大量数据展示的情况下，这能够很明显的减少组件初始化的时间，那如何禁止 Vue 劫持我们的数据呢？可以通过 Object.freeze 方法来冻结一个对象，一旦被冻结的对象就再也不能被修改了。
    例如：
    export default {
        data: () => ({
            users: {}
        }),
        async created() {
            const users = await axios.get("/api/users");
            this.users = Object.freeze(users); //冻结数据
        }
    };

    5:事件的销毁
    Vue 组件销毁时，会自动清理它与其它实例的连接，解绑它的全部指令及事件监听器，但是仅限于组件本身的事件。 
    如果在 js 内使用 addEventListene 等方式是不会自动销毁的，
    我们需要在组件销毁时手动移除这些事件的监听，以免造成内存泄露，如：
    created(){
        addEventListener('click',this.click,false);
    }
    beforeDestroy(){
        removeEventListener('click',this.click,false);
    }
   
    6:图片资源懒加载
    对于图片过多的页面，为了加速页面加载速度，所以很多时候我们需要将页面内未出现在可视区域内的图片先不做加载， 
    等到滚动到可视区域后再去加载。这样对于页面加载性能上会有很大的提升，也提高了用户体验。
    我们在项目中使用 Vue 的 vue-lazyload 插件：
    安装：
       npm install vue-lazyload --save-dev
    在main.js入口文件引入
       import VueLazyLoad from 'vue-lazyload' 
       Vue.use(VueLazyLoad) 或者 Vue.use(VueLazyload, {
                                            preLoad: 1.3, //预载高度比例
                                            error: 'dist/error.png', //出错图
                                            loading: 'dist/loading.gif', //加载图
                                            attempt: 1
                                        })
    使用方法：
        <img v-lazy="img.src" :key="img.src" > //直接使用     
        :key=""  必须要加，否则就会出现，页面刷新，其他内容都刷新了，但是只有图片不刷新的情况 。因为key可能相同，所以页面不会更新！！！ 
        <div v-lazy:background-image="imgLink"></div> //背景图使用
    
    7:图片懒加载
    Vue  是单页面应用，可能会有很多的路由引入 ，这样使用 webpcak 打包后的文件很大，当进入首页时，加载的资源过多，
    页面会出现白屏的情况，不利于用户体验。如果我们能把不同路由对应的组件分割成不同的代码块，
    然后当路由被访问的时候才加载对应的组件，这样就更加高效了。这样会大大提高首屏显示的速度，
    但是可能其他的页面的速度就会降下来。
    const Foo = () => import('./Foo.vue');
    const router = new VueRouter({
        routes:[
            {path:'/foo',component:Foo}
        ]
    })

    8:第三方插件的按需引入
    我们在项目中经常会需要引入第三方插件，如果我们直接引入整个插件，会导致项目的体积太大，
    我们可以借助 babel-plugin-component ，然后可以只引入需要的组件，以达到减小项目体积的目的。
    以下为项目中引入 element-ui 组件库为例：
    npm install babel-plugin-component -D
    npm install module_name -S    即    npm install module_name --save  写入dependencies
    npm install module_name -D    即    npm install module_name --save-dev 写入devDependencies
    devDependencies是开发时的依赖，上线打包时并不会出现打包进代码中，比如gulp相关的工具，只是用于开发阶段
    dependencies是线上运行时需要的依赖，打包时会被打包处理，比如angular、vue、vue-router等等，没有这些依赖，
    你的程序又怎么能跑起来呢。项目中做依赖处理的时候要注意，项目中依赖的包需要放到dependencies当中。
    将.babelrc修改为：
        {
        "presets": [["es2015", { "modules": false }]],
        "plugins": [
            [
            "component",
            {
                "libraryName": "element-ui",
                "styleLibraryName": "theme-chalk"
            }
            ]
        ]
        }
    在main.js
        import Vue from 'vue';
        import { Button, Select } from 'element-ui';
        Vue.use(Button)
        Vue.use(Select)
    
    9:服务端渲染 SSR or 预渲染
    服务端渲染是指 Vue 在客户端将标签渲染成的整个 html 片段的工作在服务端完成，
    服务端形成的 html 片段直接返回给客户端这个过程就叫做服务端渲染。
    （1）服务端渲染的优点：
        更好的 SEO： 因为 SPA 页面的内容是通过 Ajax 获取，而搜索引擎爬取工具并不会等待 Ajax 异步完成后再抓取页面内容，
        所以在 SPA 中是抓取不到页面通过 Ajax 获取到的内容；而 SSR 是直接由服务端返回已经渲染好的页面（数据已经包含在页面中），所以搜索引擎爬取工具可以抓取渲染好的页面；
        更快的内容到达时间（首屏加载更快）： SPA 会等待所有 Vue 编译后的 js 文件都下载完成后，才开始进行页面的渲染，
        下载等需要一定的时间等，所以首屏渲染需要一定的时间；SSR 直接由服务端渲染好页面直接返回显示，
        无需等待下载 js 文件及再去渲染等，所以 SSR 有更快的内容到达时间；
    （2）服务端渲染的缺点：
        更多的开发条件限制： 例如服务端渲染只支持 beforCreate 和 created 两个钩子函数，
        这会导致一些外部扩展库需要特殊处理，才能在服务端渲染应用程序中运行；并且与可以部署在
        任何静态文件服务器上的完全静态单页面应用程序 SPA 不同，服务端渲染应用程序，需要处于 Node.js server 运行环境；
        更多的服务器负载：在 Node.js 中渲染完整的应用程序，显然会比仅仅提供静态文件的 server 更加大量占用CPU 资源，因此如果你预料在高流量环境下使用，请准备相应的服务器负载，并明智地采用缓存策略。
        
        如果你的项目的 SEO 和 首屏渲染是评价项目的关键指标，那么你的项目就需要服务端渲染来帮助你实现最佳的初始加载性能和
        SEO，具体的 Vue SSR 如何实现，可以参考作者的另一篇文章《https://juejin.im/post/5cb6c36e6fb9a068af37aa35》。
        如果你的 Vue 项目只需改善少数营销页面（例如  /， /about， /contact 等）的 SEO，那么你可能需要预渲染，
        在构建时 (build time) 简单地生成针对特定路由的静态 HTML 文件。优点是设置预渲染更简单，
        并可以将你的前端作为一个完全静态的站点，具体你可以使用 prerender-spa-plugin 就可以轻松地添加预渲染 。
    
    10:怎么理解vue单向数据流
        所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定：父级 prop 的更新会向下流动到子组件中，但是反过来则不行。
        这样会防止从子组件意外改变父级组件的状态，从而导致你的应用的数据流向难以理解。
        额外的，每次父级组件发生更新时，子组件中所有的 prop 都将会刷新为最新的值。这意味着你不应该在一个子组件内部改变 prop。
        如果你这样做了，Vue 会在浏览器的控制台中发出警告。子组件想修改时，只能通过 $emit 派发一个自定义事件，父组件接收到后，由父组件修改。


    11：computed 和 watch 的区别和运用的场景？
        computed： 是计算属性，依赖其它属性值，并且 computed 的值有缓存，只有它依赖的属性值发生改变，下一次获取 computed 的值时才会重新计算 computed  的值；
        watch： 更多的是「观察」的作用，类似于某些数据的监听回调 ，每当监听的数据变化时都会执行回调进行后续操作；
        运用场景

        1：当我们需要进行数值计算，并且依赖于其它数据时，应该使用 computed，因为可以利用 computed 的缓存特性，避免每次获取值时，都要重新计算；

        2：当我们需要在数据变化时执行异步或开销较大的操作时，应该使用 watch，使用 watch 选项允许我们执行异步操作 ( 访问一个 API )，
        限制我们执行该操作的频率，并在我们得到最终结果前，设置中间状态。这些都是计算属性无法做到的。


    12：直接给一个数组项赋值，Vue 能检测到变化吗？
        由于 JavaScript 的限制，Vue 不能检测到以下数组的变动：

        当你利用索引直接设置一个数组项时，例如：vm.items[indexOfItem] = newValue
        当你修改数组的长度时，例如：vm.items.length = newLength

        为了解决第一个问题，Vue 提供了以下操作方法：

        // Vue.set
        Vue.set(vm.items, indexOfItem, newValue)
        // vm.$set，Vue.set的一个别名
        vm.$set(vm.items, indexOfItem, newValue)
        // Array.prototype.splice
        vm.items.splice(indexOfItem, 1, newValue)


    13：在哪个生命周期内调用异步请求
        可以在钩子函数 created、beforeMount、mounted 中进行调用，因为在这三个钩子函数中，data 已经创建，可以将服务端端返回的数据进行赋值。
        但是本人推荐在 created 钩子函数中调用异步请求，因为在 created 钩子函数中调用异步请求有以下优点：能更快获取到服务端数据，减少页面 loading 时间；
        ssr 不支持 beforeMount 、mounted 钩子函数，所以放在 created 中有助于一致性；


    14：组件中 data 为什么是一个函数？
        为什么组件中的 data 必须是一个函数，然后 return 一个对象，而 new Vue 实例里，data 可以直接是一个对象？
        Vue.component('atricle',{
            template:'<div>你好</div>',
            data(){
                return {

                }
            }
        })
        new Vue({
            el:"#app"
            data:{

            }
        })
        因为组件是用来复用的，且 JS 里对象是引用关系，如果组件中 data 是一个对象，那么这样作用域没有隔离，子组件中的 data 属性值会相互影响，
        如果组件中 data 选项是一个函数，那么每个实例可以维护一份被返回对象的独立的拷贝，组件实例之间的 data 属性值不会互相影响；而 new Vue 的实例，
        是不会被复用的，因此不存在引用对象的问题。


    15: Vue 组件间通信有哪几种方式？
        Vue 组件间通信是面试常考的知识点之一，这题有点类似于开放题，你回答出越多方法当然越加分，表明你对 Vue 掌握的越熟练。Vue 组件间通信只要指以下 3 类通信：
        父子组件通信、隔代组件通信、兄弟组件通信，下面我们分别介绍每种通信方式且会说明此种方法可适用于哪类组件间通信。
        （1）props / $emit 适用 父子组件通信
        （2）ref 与 $parent / $children 适用 父子组件通信
        （3）EventBus （$emit / $on） 适用于 父子、隔代、兄弟组件通信
            这种方法通过一个空的 Vue 实例作为中央事件总线（事件中心），用它来触发事件和监听事件，从而实现任何组件间的通信，包括父子、隔代、兄弟组件。
        （4）Vuex 适用于 父子、隔代、兄弟组件通信
        

    16:你使用过 Vuex 吗？
        Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。每一个 Vuex 应用的核心就是 store（仓库）。
        “store” 基本上就是一个容器，它包含着你的应用中大部分的状态 ( state )。
       （1）Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候，若 store 中的状态发生变化，那么相应的组件也会相应地得到高效更新。
       （2）改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation。这样使得我们可以方便地跟踪每一个状态的变化。
        State：定义了应用状态的数据结构，可以在这里设置默认的初始状态。
        Getter：允许组件从 Store 中获取数据，mapGetters 辅助函数仅仅是将 store 中的 getter 映射到局部计算属性。
        Mutation：是唯一更改 store 中状态的方法，且必须是同步函数。
        Action：用于提交 mutation，而不是直接变更状态，可以包含任意异步操作。
        Module：允许将单一的 Store 拆分为多个 store 且同时保存在单一的状态树中。


    17:vue-router 路由模式有几种？
        hash:  使用 URL hash 值来作路由。支持所有浏览器，包括不支持 HTML5 History Api 的浏览器；
        history :  依赖 HTML5 History API 和服务器配置。具体可以查看 HTML5 History 模式；
        abstract :  支持所有 JavaScript 运行环境，如 Node.js 服务器端。如果发现没有浏览器的 API，路由会自动强制进入这个模式.

    18:能说下 vue-router 中常用的 hash 和 history 路由模式实现原理吗？
        （1）hash 模式的实现原理
        早期的前端路由的实现就是基于 location.hash 来实现的。其实现原理很简单，location.hash 的值就是 URL 中 # 后面的内容。
            hash 路由模式的实现主要是基于下面几个特性：
                URL 中 hash 值只是客户端的一种状态，也就是说当向服务器端发出请求时，hash 部分不会被发送；
                hash 值的改变，都会在浏览器的访问历史中增加一个记录。因此我们能通过浏览器的回退、前进按钮控制hash 的切换；
                可以通过 a 标签，并设置 href 属性，当用户点击这个标签后，URL 的 hash 值会发生改变；
                或者使用  JavaScript 来对 loaction.hash 进行赋值，改变 URL 的 hash 值；
                我们可以使用 hashchange 事件来监听 hash 值的变化，从而对页面进行跳转（渲染）。
        （2）history 模式的实现原理
            HTML5 提供了 History API 来实现 URL 的变化。其中做最主要的 API 有以下两个：history.pushState() 和 history.repalceState()。
            这两个 API 可以在不进行刷新的情况下，操作浏览器的历史纪录。唯一不同的是，前者是新增一个历史记录，后者是直接替换当前的历史记录，
            pushState 和 repalceState 两个 API 来操作实现 URL 的变化 ；
            我们可以使用 popstate 事件来监听 url 的变化，从而对页面进行跳转（渲染）；
            history.pushState() 或 history.replaceState() 不会触发 popstate 事件，这时我们需要手动触发页面跳转（渲染）。
    

    19:什么是 MVVM？
        Model–View–ViewModel （MVVM） 是一个软件架构设计模式，由微软 WPF 和 Silverlight 的架构师 Ken Cooper 和 Ted Peters 开发，
        是一种简化用户界面的事件驱动编程方式。由 John Gossman（同样也是 WPF 和 Silverlight 的架构师）于2005年在他的博客上发表
        MVVM 源自于经典的 Model–View–Controller（MVC）模式  ，MVVM 的出现促进了前端开发与后端业务逻辑的分离，
        极大地提高了前端开发效率，MVVM 的核心是 ViewModel 层，它就像是一个中转站（value converter），
        负责转换 Model 中的数据对象来让数据变得更容易管理和使用，该层向上与视图层进行双向数据绑定，向下与 Model 层通过接口请求进行数据交互，起呈上启下作用。
        （1）View 层
            View 是视图层，也就是用户界面。前端主要由 HTML 和 CSS 来构建 。
        （2）Model 层
            Model 是指数据模型，泛指后端进行的各种业务逻辑处理和数据操控，对于前端来说就是后端提供的 api 接口。
        （3）ViewModel 层
            ViewModel 是由前端开发人员组织生成和维护的视图数据层。在这一层，前端开发者对从后端获取的 Model 数据进行转换处理，
            做二次封装，以生成符合 View 层使用预期的视图数据模型。需要注意的是 ViewModel 所封装出来的数据模型包括视图的状态和行为两部分，
            而 Model 层的数据模型是只包含状态的，比如页面的这一块展示什么，而页面加载进来时发生什么，点击这一块发生什么，这一块滚动时发生什么这些都属于视图行为（交互），
        视图状态和行为都封装在了 ViewModel 里。这样的封装使得 ViewModel 可以完整地去描述 View 层。
        MVVM 框架实现了双向绑定，这样 ViewModel 的内容会实时展现在 View 层，前端开发者再也不必低效又麻烦地通过操纵 DOM 去更新视图，
        MVVM 框架已经把最脏最累的一块做好了，我们开发者只需要处理和维护 ViewModel，更新数据视图就会自动得到相应更新。
        这样 View 层展现的不是 Model 层的数据，而是 ViewModel 的数据，由 ViewModel 负责与 Model 层交互，这就完全解耦了 View 层和 Model 层，
        这个解耦是至关重要的，它是前后端分离方案实施的重要一环。 

 */
    
    
</script>
</body>
</html>